Cuando genera un gráfico, ggplot nos hace la vida más fácil tomando un montón de decisiones por nosotros: el color de los ítems con los que representamos los datos, el rango numérico de los ejes (en que número empiezan y terminan), el color de fondo, etc. Esto nos permite ver como lucen los datos tipeando lo menos posible, pero a la hora de compartir nuestras visualizaciones con el mundo vale la pena hacer ajustes para obtener aún mejores mejores resultados.
Ahora iremos más allá del aspecto que tienen los gráficos de ggplot por defecto, puliéndolos para para su publicación.
Para empezar, activamos ggplot2:
library(ggplot2)
El data set con el que practicaremos fue compilado por Gapminder, una ONG sueca dedicada a explicar el mundo con datos. Contiene indicadores de desarrollo de países en todo el mundo, con observaciones en intervalos de 5 años:
head(gapminder)
## pais continente año expVida pobl PBIpc
## 1 Afghanistan Asia 1952 28.801 8425333 779.4453
## 2 Afghanistan Asia 1957 30.332 9240934 820.8530
## 3 Afghanistan Asia 1962 31.997 10267083 853.1007
## 4 Afghanistan Asia 1967 34.020 11537966 836.1971
## 5 Afghanistan Asia 1972 36.088 13079460 739.9811
## 6 Afghanistan Asia 1977 38.438 14880372 786.1134
Los gráficos de dispersión, o scatterplots, son quizás el tipo de visualización más conocido. Consisten en puntos proyectados en un eje de coordenadas, donde cada punto representa una observación. Son útiles para mostrar la correlación entre dos variables numéricas.
Por ejemplo, podríamos comparar la relación entre la riqueza de los países (medida como PBI per capita) y la salud de sus habitantes (como expectativa de años de vida). Esa es la visualización de datos que popularizó Hans Rosling.
Hans Rosling fue el médico, fanático de la estadística, optimista y entusiasta comunicador que fundó el proyecto Gapminder, cuya misión declarada es “luchar contra la ignorancia devastadora con una visión del mundo basada en hechos, que todo el mundo pueda entender”.
Antes de seguir, dediquemos cuatro minutos a mirar “200 años, 200 países, 4 minutos”, un resumen ilustrado de los últimos dos siglos de desarrollo económico que Rosling preparó con ayuda de la BBC.
Intentemos reproducir la visualización de Rosling. Con lo que
aprendimos hasta aquí, deberíamos poder realizar un gráfico de
dispersión básico -con geom_point()- ubicando la variable
“PBIpc” en el eje de las \(x\), y
“expVida” en el eje de las \(y\):
ggplot(gapminder, aes(x = PBIpc, y = expVida)) +
geom_point()
Hmmm… ¡no se ve muy parecido!. Lo que ocurre es que Rosling utiliza un truco : usa una escala logarítmica. Si revisamos la imagen de Rosling presentando que aparece más arriba, vemos que en el eje de las \(y\) la escala salta de 400 a 4.000, y luego a 40.000: en segmentos del mismo largo, los valores no crecen proporcionalmente sino en potencias de 10. Esto se logra tomando el logaritmo en base 10 de los datos antes de proyectarlos, con lo cual lo que estamos mostrando es 4 x 101, 4 x 102, 4 x 103, etc. ¿Para que sirve esto? Para visualizar datos en los que existen grandes disparidades en una variable, porque permite que los valores pequeños tengan espacio para diferenciarse, y a la vez que los muy grandes no aparezcan tan alejados, logrando un gráfico más compacto y en general más legible. Cuando hay dinero involucrado, suele ser necesario invocar a la escala logarítmica para mejorar la legibilidad… ¡las grandes disparidades abundan!
En todo caso, ggplot2 incluye varias funciones para
transformar las escala de las \(x\) o
de las \(y\), entre ellas las que pasan
las variables a escala logarítmica de base 10:
scale_x_log10() y scale_y_log10(). ¿Cual
usaríamos aquí?
ggplot(gapminder, aes(x = PBIpc, y = expVida)) +
geom_point() +
scale_x_log10()
Las funciones que empiezan con scale_y_ o
scale_x_ generalmente me van a servir para modificar
valores en una escala. A su vez, tiene varios parámetros modificables.
Por ejemplo, tenemos el parámetro labels en el cual puedo
especificar las etiquetas del eje. Con la ayuda del paquete
scales podemos mostrar nuestros valores como números con
comas.
ggplot(gapminder, aes(x = PBIpc, y = expVida)) +
geom_point() +
scale_x_log10(labels=scales::comma)
Ahora se vislumbra una disposición similar a la del gráfico del video, pero en nuestro caso parece haber demasiados puntos. Esto es porque nuestra data incluye 12 observaciones para cada país (cada 5 años desde 1952 hasta 2007). ¡O sea que cada país aparece 12 veces! El Dr. Rosling usaba sólo un punto por país, usando animación para mostrar distintos años.
Vamos a filtrar los datos para quedarnos sólo con la observación más
reciente disponible, la de 2007. Hay muchas, pero muchas maneras de
hacer eso en R, pero aquí vamos a usar la función filter()
del paquete dplyr. Si no
conocen esta herramienta, o necesitan un repaso rápido, pueden revisar
“transformando
los datos”. Por ahora la vamos a usar de forma muy simple: en lugar
de llamar a la variable gapminder, vamos a usar el resultado de filtrar
su contenido de forma tal que solo usemos las filas donde la variable
“año” toma el valor 2007.
Activamos el paquete dplyr
library(dplyr)
Y ahora ya podemos usar su función filter() para
modificar nuestro dataset:
ggplot(filter(gapminder, año == 2007), aes(x = PBIpc, y = expVida)) +
geom_point() +
scale_x_log10()
Ahí va queriendo. Desviándonos un poco de nuestro objetivo original
de replicar a nuestro amigo Hans, podríamos querer agregar un intervalo
de confianza para nuestros puntos. Esto lo podemos lograr con
geom_smooth().
ggplot(filter(gapminder, año == 2007), aes(x = PBIpc, y = expVida)) +
geom_point() +
geom_smooth(method = "lm") +
scale_x_log10()
## `geom_smooth()` using formula = 'y ~ x'
geom_smooth() me muestra una línea de tendencia de las
observaciones en el gráfico con el intervalo de confianza para cada
grupo. Cuantas menos observaciones tenga para cada grupo, más grande
será el intervalo. Puedo aplicar distintas funciones para aplicar la
línea de tendencia.
Necesitamos dos ajustes más para que el parecido sea evidente. Rosling usa el tamaño de los puntos para indicar la población del país, y el color para diferenciar entre continentes. Con lo que aprendimos en la clase anterior, podemos animarnos: las variables que que se mostrarán con esos atributos estéticos son “pobl” y “continente”
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl, color = continente)) +
geom_point() +
scale_x_log10()
¡Eso si es un plot gapmindereano!
¿Qué pasaría si yo quisiera agregar mi intervalo de confianza en este gráfico?
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl, color = continente)) +
geom_point() +
geom_smooth(method = "lm")+
scale_x_log10()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `geom_smooth()` using formula = 'y ~ x'
## Warning in qt((1 - level)/2, df): NaNs produced
## Warning: The following aesthetics were dropped during statistical transformation: size
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
## the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
## variable into a factor?
## Warning in max(ids, na.rm = TRUE): ningun argumento finito para max; retornando
## -Inf
:( ¿Por qué no se ve como yo quiero?
Esto tiene que ver con el mapeo de las observaciones. En la primera
línea de ggplot, yo definí que quería que los atributos de color y
tamaño se mapeen para todas las geometrías que tengo. De esta manera,
geom_smooth()`me está haciendo la línea de tendencia para
todos los grupos por su color. Por ello, si queremos mantener la
tendencia global debemos hacer unos pequeños ajustes en nuestro
gráfico.
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida)) +
geom_point(aes(size = pobl, color = continente)) +
geom_smooth(method = "lm")+
scale_x_log10()
## `geom_smooth()` using formula = 'y ~ x'
Pero esto no termina aquí. Quedan bastantes cosas para ajustar antes de que nuestro querido gráfico esté listo para ser compartido con el mundo.
En el video de la BBC, la visualización se presenta sin leyendas para
reducir al mínimo el texto en pantalla, y que se pueda mirar como casi
un cuadro. Para eliminar leyendas en nuestro ggplot, podemos usar
guides, y dentro asignar el valor "none" a
cada atributo estético para el cual no queremos leyenda - por ejemplo,
color = "none". ¿Cómo eliminamos la leyenda de los
atributos color y tamaño?
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl, color = continente)) +
geom_point() +
scale_x_log10() +
guides(size = "none", color = "none")
Con frecuencia preferimos quedarnos con la leyenda, para invitar a la audiencia a que compare los valores representados. En ese caso vale la pena asegurarse de que los valores mostrados son razonablemente fáciles de interpretar.
En nuestro gráfico, algo que podemos mejorar es la representación de los números muy grandes: esos millones de personas de la población. Por defecto, R usa notación científica para abreviar los números muy grandes (o muy anchos, deberíamos decir: los números ínfimos, con muchos espacios decimales en cero, también se abrevian). Por eso en nuestro gráfico aparecen valores como “1.25e+09”, que significa “1,25 * 10 elevado a la 9”, o sea 1.250 millones. La brevedadad es buena a la hora de comunicar, pero no cuando se trata de números - la escala para la variable de población, en notación científica, resulta muy difícil de interpretar para la mayoría de las personas:
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl, color = continente)) +
geom_point() +
scale_x_log10()
Se puede hacer que R se abstenga de abreviar usando la notación
científica (ejecutando la línea options(scipen=999)). Pero
una solución aún mejor, cuando de visualización se trata, es la de
convertir unidades. Por ejemplo, si los valores de población alcanzan
valores tan grandes, representemos millones de personas y con
eso los valores van a ser mas pequeños: en lugar de mostrar 1.000.000,
ahora veremos solo “1” (porque la unidad es “millones de
habitantes”).
Entonces nos quedamos con las leyendas (ya no las deshabilitamos con
guides()), y le podemos pedirle a ggplot que el tamaño de
los puntos ya no represente los valores de la columna “pobl”, si no el
resultado de los valores de la columna divididos por un millón. ¿Cómo
sería?
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10()
Si comparamos nuestros resultados con los de la visualización
televisada por la BBC, notaremos que los colores usados para representar
la variable “continente” son distintos. Esto es porque Rosling y su
equipo los eligieron a su gusto, mientras que nosotros estamos usando
los colores que elige ggplot de forma automática. En la próxima clase
veremos como usar escalas de colores pre-diseñadas, con colores elegidos
por sus propiedades ideales para usar en visualización. Pero ahora mismo
tenemos la oportunidad de elegir de forma explícita los colores que
queremos usar, para replicar los del ejemplo. Al cumplirse un minuto del video, oímos a
Hans nombrar los colores que usa: “Europe brown, Asia Red…”. Con eso
podemos armar una lista con valores y sus colores asignados para pasarle
a ggplot(). Tomándonos nada menos que tres licencias: no
vamos a asignarle un color específico a los países del Medio Oriente
porque no aparecen diferenciados en nuestra data, vamos a elegir un
color para los dos países de Oceanía que tenemos, y para Europa vamos a
elegir un naranja oscuro, porque ese parece ser el color que usaron en
lugar de marrón.
En R tenemos nada menos que 657 colores que podemos llamar por nombre, como “hotpink” o “aquamarine”. COmo es imposible recordarlos a todos, se puede recurrir a una guía de colores para encontrar los que necesitamos. Habiendo identificado nuestros colores, definimos la escala manual así:
color_continentes <- c("Europe" = "darkorange", "Asia" = "red", "Africa" = "blue",
"Americas" = "yellow", "Oceania" = "purple")
Y luego podemos usarla para indicarle a ggplot los colores exactos
que queremos usar. Para eso sirve scale_color_manual(), con
su parámetro “values”. Probemos:
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes)
Hasta aquí hemos podido ajustar los atributos gráficos que dependen estrechamente de los datos, como las escalas, los colores y los tamaños. ¿Pero que hay de otros componentes visuales que podríamos cambiar, como la tipografía y su tamaño o el color de fondo?
Esos atributos corresponden al “tema” (theme en inglés) que
apliquemos. Los temas son como “packs” de parámetros que definen el
aspecto de todos los componentes no relacionados con los datos, y pueden
cambiar drásticamente el look de nuetros gráficos. El paquete
ggplot2 incluye varias funciones que aplican temas
predefinidos, como “theme_dark()” (la opción por defecto),
“theme_dark”, “theme_void”, “theme_minimal”, etc. Por eso para usar los
temas alcanza con sumar una línea que llame a la función
correspondiente. Por ejemplo, para usar “theme_dark”:
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_dark()
Incluso podemos definir a mano cada componente visual de los
gráficos, creando así nuestro propio tema. El camino comienza por el uso
de la función
theme. Si entramos a la documentación vemos que tenemos
muuuuuchos parámetros que podemos modificar en ella, por lo que
abordarlos en su totalidad es un poco difícil, ya que el uso que le
demos va a depender de lo que querramos hacer. Veamos un pequeño
ejemplito, cambiando de lugar la ubicación de las leyendas con el
parámetro legend.position.
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_dark()+
theme(legend.position = "bottom")
(¡Ojo! Si quiero aplicar un parámetro de la función theme manteniendo un theme predeterminado, siempre tiene que ir después de él).
También existen paquetes de R que agregan a nuestro arsenal nuevos
temas listos para usar, como ggthemes
y hrbrthemes.
Para cerrar el tema de los temas (¡ja!) va una recomendación. El tema
minimalista “theme_minimal” viene con ggplot2 por lo que ya
lo tenemos disponible, y con sólo agregar la línea que lo aplica
obtenemos un gráfico de aspecto más “limpio” que el del tema por
default:
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_minimal()
¿Cómo haríamos si quisiera ver dónde se ubican los países de las Americas en este mapa?
Vamos a introducir un paquete bastante útil: ggrepel.
Está diseñado específicamente para poder agregar etiquetas en gráficos
de ggplot sin que se nos pisen entre ellas. Probemos usar su función
geom_text_repel:
library(ggrepel)
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
geom_text_repel(data = gapminder %>% filter(año == 2007 & continente == "Americas"),
aes(x = PBIpc, y = expVida, label = pais), inherit.aes = FALSE)+
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_minimal()
## Warning: ggrepel: 7 unlabeled data points (too many overlaps). Consider
## increasing max.overlaps
Aquí estamos viendo algunos parámetros nuevos:
label me permite agregar las etiquetas de cada
país,
inherit.aes evita que mi texto adopte los colores y
formas de la primera línea.
En el contexto de la exploración de los datos, lo importante es trabajar en forma rápida. Probamos una u otra técnica de visualización y refinamos nuestros resultados hasta hallar patrones interesantes, o sacarnos dudas acerca del contenido. No necesitamos ponerle título a las visualizaciones, porque ya sabemos de que tratan (¡acabamos de escribirlas!). No nos preocupa que los nombres de los ejes indiquen en forma clara la variable representan, porque ya lo sabemos de antemano.
Pero cuando queremos guardar un gráfico para compartir con otras personas, sea publicándolo en un paper o enviándolo por email a colegas, necesitamos tener más cuidado. Hemos pasado del ámbito de la exploración al de la comunicación. Ahora si nos importa la claridad, porque no sabemos de antemano cuánta familiaridad tiene con los datos la eventual audiencia.
Si bien la comunicación clara es un arte cuyas reglas dependen del contexto, y además cada quien tiene su estilo, podemos mencionar al menos tres elementos que no deberían faltar en un gráfico pensado para compartir:
y ya que estamos, dos opcionales:
Todo ello puede resolverse con la misma función: labs().
Esta se encarga de definir título y subtítulo, nombres de ejes y de
leyendas, y nota al pie si hiciera falta.
Empecemos por título y subtítulo, que se asignan con los parámetros title y subtitle. Pidamos que el título sea “Riqueza vs. salud en los países del mundo”, y el subtítulo, “según datos 2007”:
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos 2007")
Ahora mejoremos las etiquetas de los ejes, y de la leyenda de tamaño.
La leyenda dec color está bien así, la vamos a dejar en paz. Como
podríamos habernos imaginado, el parámetro de labs() para
definir el nombre que llevaran las \(x\) es x (como
labs(x = "nombre para el eje de las x")). El parámetro
para las \(y\) es y. El que
fija el título de la leyenda de tamaño es size, y así con el
resto de las leyendas: color, shape, o el atributo que
hayamos definido dentro de aes() al hacer el gráfico.
Pongamos “población (millones)” como título de la leyenda de tamaño, “PBI per capita (USD)” en las \(x\), y “expectativa de vida en años” en las \(y\):
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida)) +
geom_point(aes(size = pobl/1000000, color = continente)) +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo",
subtitle = "según datos 2007",
size = "población (millones)",
x = "PBI per capita (USD)",
y = "expectativa de vida en años")
Con el paquete ggtext también puedo formatear con Markdown el texto que aparece:
library(ggtext)
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida)) +
geom_point(aes(size = pobl/1000000, color = continente)) +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo",
subtitle = "según datos 2007",
size = "población (millones)",
x = "**PBI per capita** (USD)",
y = "**expectativa de vida** en años")+
theme(plot.title = element_markdown(face = 'bold'),
axis.title.x = element_markdown(),
axis.title.y = element_markdown())
Y por último, una nota al pie con la fuente de los datos, vía caption :
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos 2007",
size = "población (millones)",
x = "**PBI per capita** (USD)",
y = "**expectativa de vida** en años",
caption = "*fuente: Gapminder*") +
theme(plot.title = element_markdown(face = 'bold'),
plot.caption = element_markdown(lineheight = 1.2),
axis.title.x = element_markdown(),
axis.title.y = element_markdown())
ggsave('grafico_gapminder.png', width = 8, height = 5)
Y con eso, terminamos por ahora. Para seguir practicando, tenemos en Data Visualization - A practical introduction de Kieran Healy el capítulo: “Refine your plots”: en el que está basado esta clase.